package lumis.service.wsrp.producer;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import lumis.portal.authentication.SessionConfig;
import lumis.portal.authentication.http.IHttpAuthenticator;
import lumis.portal.dao.DaoException;
import lumis.portal.event.persistence.PortalEventEntityListener;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.serviceinterfaceinstance.IServiceInterfaceInstanceManager;
import lumis.portal.transaction.ITransaction;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.service.wsrp.common.WSRPUtils;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.NamedQueries;
import org.hibernate.annotations.NamedQuery;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;

import com.sun.portal.wsrp.common.LeaseTime;
import com.sun.portal.wsrp.common.stubs.v2.PropertyDescription;
import com.sun.portal.wsrp.producer.AbstractProducer;
import com.sun.portal.wsrp.producer.Producer;
import com.sun.portal.wsrp.producer.ProducerException;
import com.sun.portal.wsrp.producer.ProducerVersion;
import com.sun.portal.wsrp.producer.ProfileMapManager;
import com.sun.portal.wsrp.producer.driver.PortletRegistry;
import com.sun.portal.wsrp.producer.markup.MarkupManager;
import com.sun.portal.wsrp.producer.portletmanagement.PortletManagementManager;
import com.sun.portal.wsrp.producer.registration.RegistrationManager;
import com.sun.portal.wsrp.producer.registration.RegistrationRecord;
import com.sun.portal.wsrp.producer.servicedescription.ServiceDescriptionManager;

/**
 * {@link Producer} implementation.
 * 
 * @version $Revision: 13092 $ $Date: 2011-05-28 18:19:06 -0300 (Sat, 28 May 2011) $
 * @since 5.5.0
 */
@Entity
@Table(name = "lum_WsrpPProducer")
@GenericGenerator(name = "portal-guid", strategy = "lumis.portal.dao.hibernate.GuidGenerator")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@EntityListeners(value = PortalEventEntityListener.class)
@NamedQueries({
        @NamedQuery(name = ProducerImpl.NAMED_QUERY_FIND_PRODUCER_BY_NAMESPACE, query = "SELECT producer FROM ProducerImpl producer WHERE producer.namespace = :namespace"), 
        @NamedQuery(name = ProducerImpl.NAMED_QUERY_FIND_PRODUCER_BY_PRODUCER_KEY, query = "SELECT producer FROM ProducerImpl producer WHERE producer.producerKey = :producerKey", cacheable = true),
        @NamedQuery(name = ProducerImpl.NAMED_QUERY_FIND_PRODUCERS_BY_OFFERED_PORTLETS, query = "SELECT producer FROM ProducerImpl producer inner join producer.offeredPortlets portlet WHERE portlet.interfaceInstanceId = :interfaceInstanceId")
    })
@SuppressWarnings("serial")
class ProducerImpl extends AbstractProducer implements Serializable
{
	/**
	 * Query for returning all producers in a namespace.
	 * @since 5.5.0
	 */
	static final String NAMED_QUERY_FIND_PRODUCER_BY_NAMESPACE = "findProducerImplByNamespace";
	
	/**
	 * Query for returning producer using its producer key.
	 * @since 5.5.0 
	 */
	static final String NAMED_QUERY_FIND_PRODUCER_BY_PRODUCER_KEY = "findProducerImplByProducerKey";
	
	/**
	 * Query for returning producers that contains an offered portlet.
	 * @since 5.5.0 
	 */
	static final String NAMED_QUERY_FIND_PRODUCERS_BY_OFFERED_PORTLETS = "findProducerImplsByOfferedPortlets";

	@Id
	@GeneratedValue(generator = "portal-guid")
	private String id;

	@Column(name="producerKey")
	private String producerKey;
	
	@Column(name="serviceInstanceId")
	private String namespace;
	
	@Type(type = "lumis.portal.dao.hibernate.GenericEnumUserType", parameters = { @Parameter(name = "enumClass", value = "com.sun.portal.wsrp.producer.ProducerVersion"),
			@Parameter(name = "identifierMethod", value = "value"), @Parameter(name = "valueOfMethod", value = "fromValue") })
	@Column(name = "version")
	private ProducerVersion version;
	
	private boolean enabled;

	private String httpAuthenticator;
	
	private boolean requiresRegistration;
	
	private boolean supportsInbandRegistration;
	
	private long maxInbandLifetime;
	
    @OneToMany(mappedBy="producer", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Set<RemoteConsumerRegistration> registrations = new HashSet<RemoteConsumerRegistration>();


	@OneToMany(mappedBy="producer", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Set<OfferedPortlet> offeredPortlets = new HashSet<OfferedPortlet>();
	

	/**
	 * Returns the surrogate identifier of this producer.
	 * @return the surrogate identifier.
	 * @since 5.5.0
	 */
	public String getId()
	{
		return id;
	}

	public Set<String> getOfferedPortletNames() throws ProducerException
	{
		Set<String> offeredName = new HashSet<String>(this.offeredPortlets.size());
	
		for (OfferedPortlet offeredPortlet : this.offeredPortlets) {
			offeredName.add(offeredPortlet.getInterfaceInstanceId());
		}
		
		return offeredName;
	}
	
	public String getPortalId()
	{
		return null;
	}
	
	public String getNamespace()
	{
		return this.namespace;
	}

	public String getProducerKey()
	{
		return this.producerKey;
	}
	
	public void setProducerKey(String producerKey)
	{
		this.producerKey = producerKey;
	}
	
	public void setNamespace(String namespace)
	{
		this.namespace = namespace;
	}

	public ProducerVersion getVersion() throws ProducerException
	{
		return this.version;
	}

	public boolean isEnabled() throws ProducerException
	{
		return this.enabled;
	}
	
	public void setIsEnabled(boolean enabled) throws ProducerException
	{
		this.enabled = enabled;
	}

	/**
	 * Returns the class name of the {@link IHttpAuthenticator} implementation
	 * to be used for this producer.
	 * @return the HTTP authenticator class name.
	 * @since 5.5.0
	 */
	public String getHttpAuthenticator()
	{
		return this.httpAuthenticator;
	}
	
	/**
	 * Sets the class name of the {@link IHttpAuthenticator} implementation
	 * to be used for this producer.
	 * @param httpAuthenticator the HTTP authenticator class name.
	 * @since 5.5.0
	 */
	public void setHttpAuthenticator(String httpAuthenticator)
	{
		this.httpAuthenticator = httpAuthenticator;
	}
	
	public void setOfferedPortletNames(Set<String> names) throws ProducerException
	{
		Iterator<OfferedPortlet> iterator = this.offeredPortlets.iterator();
		
		OfferedPortlet portlet;
		while(iterator.hasNext())
		{
			portlet = iterator.next();
			if(!names.contains(portlet.getInterfaceInstanceId()))
			{
				iterator.remove();
			}
		}
		
		Set<String> offeredPorletNames = new HashSet<String>();
		for (OfferedPortlet offeredPortlet: this.offeredPortlets)
			offeredPorletNames.add(offeredPortlet.getInterfaceInstanceId());
		
		for (String interfaceInstanceId : names)
		{
			if(!offeredPorletNames.contains(interfaceInstanceId))
			{
				portlet = new OfferedPortlet();
				portlet.setInterfaceInstanceId(interfaceInstanceId);
				portlet.setProducer(this);
				this.offeredPortlets.add(portlet);
			}
		}
		
	}

	public void setPortalId(String portalId)
	{
		throw new UnsupportedOperationException();
	}

	public void setVersion(ProducerVersion version) throws ProducerException
	{
		this.version = version;
	}

	// -- Managers -- //
	
	public ProfileMapManager getProfileMapManager() throws ProducerException
	{
		return new ProfileMapManagerImpl();
	}
	
	@Override
	public PortletRegistry getPortletRegistry() throws ProducerException
	{
		return new PortletRegistryImpl(this);
	}
	
	@Override
	public MarkupManager getMarkupManager(ServletContext context, HttpServletRequest request) throws ProducerException
	{
		return new MarkupManagerImpl(request, context, this);
	}

	@Override
	public ServiceDescriptionManager getServiceDescriptionManager() throws ProducerException
	{
		return new ServiceDescriptionManagerImpl(this);
	}

	// -- Registration read methods -- //

	public boolean requiresRegistration() throws ProducerException
	{
		return this.requiresRegistration;
	}
	public boolean inbandRegistrationSupported() throws ProducerException
	{
		return this.supportsInbandRegistration;
	}
	
	public LeaseTime getRegistrationLifetime(String registrationHandle) throws ProducerException
	{	
		
		LeaseTime lease = null;
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class, registrationHandle);
			if(registration != null){
				lease = registration.getLifetime();
			}
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try {
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
		
		return lease;
	}
	
	/**
	 * Verifies if a registrationHandle exists and is active (lifetime has not expired).
	 * @param registrationHandle to be verified.
	 * @return true if the registrationHandle exists and is active, false otherwise.
	 */
	public boolean isValidRegistration(String registrationHandle) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		boolean valid = false;
		try
		{
			
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class, registrationHandle);
			transaction.commit();
			if(registration != null)
			{
				valid = true;
			}
			
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try {
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
		return valid;
	}
	public RegistrationRecord getRegistrationRecord(String registrationHandle) throws ProducerException
	{
		if(!isValidRegistration(registrationHandle))
		{
			throw new ProducerException("Invalid registrationHandle " + registrationHandle);
		}
		
		RegistrationRecord record = null;
		ITransaction transaction = PortalTransactionFactory.getCurrentTransaction();
		try
		{
			
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class, registrationHandle);
			transaction.commit();
			
			record = registration.getRegistrationRecord();

			
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try {
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
		
		return record;
	}
	public Set<PropertyDescription> getRegistrationPropertyDescriptions(Set<String> desiredLocales) throws ProducerException
	{
		return Collections.emptySet();
	}


	public Set<RegistrationRecord> getRegistrationRecords() throws ProducerException
	{
		Set<RegistrationRecord> records = new HashSet<RegistrationRecord>();
		for (RemoteConsumerRegistration registration : registrations) 
		{
			records.add(registration.getRegistrationRecord());
			
		}
		return records;
	}

	public boolean getRegistrationStatus(String registrationHandle) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		boolean status = false;
		
		try
		{
			
			transaction.begin();
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class, registrationHandle);
			if(registration != null)
			{
				status = registration.isEnabled();
			}
			else
			{
				status = false;
			}
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
		return status;
			
	}

	public String getRegistrationValidatorClassName() throws ProducerException
	{
		return "com.sun.portal.wsrp.producer.registration.validator.impl.DefaultRegistrationValidator";
	}
	
	public void setRequiresRegistration(boolean state) throws ProducerException
	{
		this.requiresRegistration = state;
	}
	
	public void setInbandRegistrationSupported(boolean supported) throws ProducerException
	{
		this.supportsInbandRegistration = supported;
	}
	
	
	@SuppressWarnings("unchecked")
	public String addRegistration(RegistrationRecord registrationRecord) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		RemoteConsumerRegistration registration = new RemoteConsumerRegistration();
		try
		{
			
			transaction.begin();
			
			
			registration.setRegistrationRecord(registrationRecord);
			registration.setConsumerModes(WSRPUtils.join(registrationRecord.getConsumerModes(), ","));
			registration.setConsumerUserScopes(WSRPUtils.join(registrationRecord.getConsumerUserScopes(), ","));
			registration.setConsumerWindowStates(WSRPUtils.join(registrationRecord.getConsumerWindowStates(), ","));
			registration.setEnabled(registrationRecord.isEnabled());
			registration.setProducer(this);
			LeaseTime lifetime = registrationRecord.getLifetime();
			registration.setLifetime(lifetime);
						
			EntityManager manager = ManagerFactory.getEntityManager();	
			ProducerImpl producer = manager.find(this.getClass(), this.id);		
			manager.persist(registration);
			producer.getRegistrations().add(registration);
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
		return registration.getRegistrationHandle();
	}
	public void addRegistrationPropertyDescription(PropertyDescription pd) throws ProducerException
	{
		//IGNORE
	}

	@SuppressWarnings("unchecked")
	public void modifyRegistration(RegistrationRecord registrationRecord) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class, registrationRecord.getRegistrationHandle());
			
			registration.setConsumerAgent(registrationRecord.getConsumerAgent());
			registration.setConsumerModes(WSRPUtils.join(registrationRecord.getConsumerModes(), ","));
			registration.setConsumerName(registrationRecord.getConsumerName());
			registration.setConsumerUserScopes(WSRPUtils.join(registrationRecord.getConsumerUserScopes(), ","));
			registration.setConsumerWindowStates(WSRPUtils.join(registrationRecord.getConsumerWindowStates(), ","));
			registration.setMethodGetSupported(registrationRecord.isMethodGetSupported());
			registration.setEnabled(registrationRecord.isEnabled());
			
			manager.persist(registration);
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
	}

	public void removeRegistrationPropertyDescriptions(Set<String> names) throws ProducerException
	{
		//IGNORE
	}

	public void removeRegistrations(Set<String> registrationHandles) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			ProducerImpl producer = manager.find(this.getClass(), this.id);
			for (String registrationHandle : registrationHandles)
			{
				RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class,  registrationHandle);
				producer.getRegistrations().remove(registration);
				manager.remove(registration);
			}

			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
	}

	public void setRegistrationLifetime(String registrationHandle, LeaseTime lifetime) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class,  registrationHandle);
			registration.setLifetime(lifetime);
			manager.persist(registration);
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
	}

	public void setRegistrationStatus(String registrationHandle, boolean status) throws ProducerException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();
			
			EntityManager manager = ManagerFactory.getEntityManager();
			RemoteConsumerRegistration registration = manager.find(RemoteConsumerRegistration.class,  registrationHandle);
			registration.setEnabled(status);
			manager.persist(registration);
			
			transaction.commit();
		}
		catch (DaoException e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
	}

	public void setRegistrationValidatorClassName(String validatorClassName) throws ProducerException
	{
		//IGNORE
	}

	
	/**
	 * Removes specific offered portlet from current producer.
	 * @param interfaceInstanceId interface instance identifier of portlet to be removed.
	 * @throws ProducerException 
	 * @since 5.5.0
	 */
	public void removeOfferedPortlet(String interfaceInstanceId) throws ProducerException
	{
		
		OfferedPortlet portlet = null;
		for (OfferedPortlet offeredPortlet : this.offeredPortlets) 
		{
			if(offeredPortlet.getInterfaceInstanceId().equals(interfaceInstanceId))
			{
				portlet = offeredPortlet;
				offeredPortlets.remove(portlet);
				break;
			}
		}
		if(portlet == null){
			return;
		}

		ITransaction transaction = PortalTransactionFactory.createTransaction();
		
		try
		{
			transaction.begin();
			EntityManager entityManager = ManagerFactory.getEntityManager();
			portlet = entityManager.find(OfferedPortlet.class,  portlet.getId());
			entityManager.remove(portlet);
			IServiceInterfaceInstanceManager serviceInterfaceInstanceManager = ManagerFactory.getServiceInterfaceInstanceManager();
			SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();
			Set<ClonedPortlet> clonedPortlets = portlet.getClonedPortlets();
			for (ClonedPortlet clonedPortlet : clonedPortlets) 
			{
				serviceInterfaceInstanceManager.delete(sessionConfig, clonedPortlet.getId(), transaction);
			}
			
			transaction.commit();
		}	
		catch (Exception e) 
		{
			throw new ProducerException(e);
		}
		finally
		{
			try 
			{
				transaction.dispose();
			} 
			catch (DaoException e) 
			{
				throw new ProducerException(e);
			}
		}
	}

	/**
	 * Returns the maximum allowed inband Lifetime.
	 * @return maxInbandLifetime maximum lifetime allowed for inband registration.
	 */
	public long getMaxInbandLifetime() 
	{
		return maxInbandLifetime;
	}

	/**
	 * Sets the maximum allowed inband lifetime.
	 * @param maxInbandLifetime maximum lifetime allowed for inband registration.
	 */
	public void setMaxInbandLifetime(long maxInbandLifetime) 
	{
		this.maxInbandLifetime = maxInbandLifetime;
	}
	
	@Override
    public RegistrationManager getRegistrationManager() throws ProducerException 
    {
        return new RegistrationManagerImpl(this);
    }
    
	/**
	 * Returns the offeredPortlet for this producer.
	 * @return offeredPortlet for current producer.
	 * @since 6.0.0
	 */
    Set<OfferedPortlet> getOfferedPortlets()
    {
    	return this.offeredPortlets; 
    }
    
	/**
	 * Returns the set of Registrations associated to this producer.
	 * @return registrations associated to the producer.
	 */
	Set<RemoteConsumerRegistration> getRegistrations() 
	{
		return registrations;
	}

	/**
	 * Sets the registrations associated to the producer.
	 * @param registrations associated to this producer.
	 */
	void setRegistrations(Set<RemoteConsumerRegistration> registrations) 
	{
		this.registrations = registrations;
	}
	
    /**
     * Returns the PortletManagement Manager.
     */
	@Override
	public PortletManagementManager getPortletManagementManager() throws ProducerException 
	{
		return new PortletManagementManagerImpl(this);
	}
}